//go:build go1.21

package log

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"os"
	"runtime"
	"strings"
	"time"

	mlog "gitee.com/yrwy/go-log"
	mio "gitee.com/yrwy/msgo/pkg/io"
	"gitee.com/yrwy/msgo/pkg/strs"
	"github.com/spf13/viper"
)

type SWriter struct {
	logger slog.Handler
	w      io.Closer
}

type SlogHandle []slog.Handler

func (x SlogHandle) Enabled(ctx context.Context, lv slog.Level) bool {
	for _, h := range x {
		en := h.Enabled(ctx, lv)
		if en {
			return true
		}
	}
	return false
}

func (x SlogHandle) Handle(ctx context.Context, r slog.Record) error {
	var err error
	for _, h := range x {
		if h.Enabled(ctx, r.Level) {
			er := h.Handle(ctx, r)
			if er != nil {
				err = er
			}
		}
	}
	return err
}

func (x SlogHandle) WithAttrs(attrs []slog.Attr) slog.Handler {
	r := make(SlogHandle, len(x))
	for i, h := range x {
		r[i] = h.WithAttrs(attrs)
	}
	return r
}

func (x SlogHandle) WithGroup(name string) slog.Handler {
	r := make(SlogHandle, len(x))
	for i, h := range x {
		r[i] = h.WithGroup(name)
	}
	return r
}

func fileCut(fn string) {
	slog.Log(context.TODO(), slog.LevelInfo, "log file cut", "filename", fn)
}

func NewJsonWriter(logfile string) (*SWriter, error) {
	if logfile != "" {
		var hs SlogHandle
		var f io.WriteCloser
		var err error
		if strings.HasSuffix(logfile, ".yml") || strings.HasSuffix(logfile, ".yaml") {
			v := viper.New()
			v.SetConfigFile(logfile)
			er := v.ReadInConfig()
			if er != nil {
				fmt.Println("viper error:", err)
				return nil, er
			}
			sv := v.Sub("console")
			if sv != nil {
				level := sv.GetString("enable")
				lv := mlog.ParseLevel(level)
				if lv != mlog.NOLOG {
					hs = append(hs, slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true, Level: level2slogLevel(lv)}))
				}
			}
			sv = v.Sub("file")
			if sv != nil {
				level := sv.GetString("enable")
				lv := mlog.ParseLevel(level)
				if strs.EnableString(level) || lv != mlog.NOLOG {
					var fileropts []mlog.FilerOption
					s := sv.GetString("rootpath")
					if len(s) > 0 {
						fileropts = append(fileropts, mlog.FileRoot(s))
					}
					s = sv.GetString("maxfilesize")
					if len(s) > 0 {
						n := strs.ParseBytes(s)
						if n > 0 {
							fmt.Println("maxfilesize:", s, n)
							fileropts = append(fileropts, mlog.FileMaxSize(int(n)))
						}
					}
					n := sv.GetInt("maxsavedays")
					if n >= 0 {
						fileropts = append(fileropts, mlog.FileSaveDays(n))
					}
					fileropts = append(fileropts, mlog.FWFileCut(fileCut))
					f, err = mlog.NewFiler(fileropts...)
				}
			}
		} else if mio.IsDir(logfile) {
			f, err = mlog.NewFiler(mlog.FileRoot(logfile), mlog.FileSaveDays(360))
		} else {
			f, err = os.OpenFile(logfile, os.O_WRONLY, os.ModePerm)
		}
		if err == nil {
			h := slog.NewJSONHandler(f, &slog.HandlerOptions{AddSource: true, Level: slog.LevelDebug})
			hs = append(hs, h)
		}
		if len(hs) > 0 {
			return &SWriter{hs, f}, err
		}
	}
	return &SWriter{slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true, Level: slog.LevelDebug}), nil}, nil
}

func (x *SWriter) Close() error {
	if x != nil && x.w != nil {
		w := x.w
		x.w = nil
		x.logger = slog.Default().Handler()
		return w.Close()
	}
	return nil
}

func level2slogLevel(lv mlog.Level) slog.Level {
	switch lv {
	case mlog.DEBUG:
		return slog.LevelDebug
	case mlog.INFO:
		return slog.LevelInfo
	case mlog.WARN:
		return slog.LevelWarn
	case mlog.ERROR:
		return slog.LevelError
	case mlog.FATAL:
		return slog.LevelError
	}
	return slog.LevelDebug
}

// 日志输出全局接口
func (x *SWriter) Log(lv mlog.Level, args ...any) error {
	return x.LogDepth(2, lv, args...)
}

func (x *SWriter) Logf(lv mlog.Level, format string, args ...any) error {
	return x.LogDepthf(2, lv, format, args...)
}

func (x *SWriter) Logr(lv mlog.Level, r io.Reader) error {
	return x.LogDepthr(2, lv, r)
}

func (x *SWriter) Handler() slog.Handler {
	if x.logger != nil {
		return x.logger
	}
	return slog.Default().Handler()
}

// calldepth从0开始计算
func (x *SWriter) LogDepth(calldepth int, lv mlog.Level, args ...any) error {
	if len(args) < 1 {
		return nil
	}
	level := level2slogLevel(lv)
	h := x.Handler()
	if !h.Enabled(context.TODO(), level) {
		return nil
	}
	var pcs [1]uintptr
	runtime.Callers(calldepth+1, pcs[:])
	r := slog.NewRecord(time.Now(), level, fmt.Sprint(args[0]), pcs[0])
	if len(args) > 1 {
		r.Add(args[1:]...)
	}
	return h.Handle(context.Background(), r)
}

func (x *SWriter) LogDepthf(calldepth int, lv mlog.Level, format string, args ...any) error {
	return x.LogDepth(calldepth+1, lv, fmt.Sprintf(format, args...))
}

func (x *SWriter) LogDepthr(calldepth int, lv mlog.Level, r io.Reader) error {
	buf, err := io.ReadAll(r)
	if err != nil {
		return err
	}
	return x.LogDepth(calldepth+1, lv, string(buf))
}
